﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Engine;
using NHibernate.Persister.Entity;
using NHibernate.Proxy;
using NHibernate.Tool.hbm2ddl;
using NHibernate.Type;
using NUnit.Framework;

namespace TestBat.Models.Tests.GhostBuster
{
    [TestFixture]
    public class GhostBuster
    {
        private const string DefaultIdName = "ID";
        private Configuration cfg;
        private ISessionFactory sessions;

        [Test, Explicit]
        public void UnexpectedUpdateDeleteOnFetch()
        {
            cfg = new Configuration();
            cfg.Configure();
            cfg.AddAssembly("TestBat.Models.Tests");
            sessions = cfg.BuildSessionFactory();

            // export schema
            new SchemaExport(cfg).Execute(false, true, false);

            PersistingMappings(null);
        }

        private void PersistingMappings(ICollection<string> entitiesFilter)
        {
            var invalidUpdates = new List<string>();
            var nop = new NoUpdateInterceptor(invalidUpdates);

            IEnumerable<string> entitiesToCheck;
            if (entitiesFilter == null)
            {
                entitiesToCheck = cfg.ClassMappings.Select(x => x.EntityName);
            }
            else
            {
                entitiesToCheck = from persistentClass in cfg.ClassMappings
                                  where entitiesFilter.Contains(persistentClass.EntityName)
                                  select persistentClass.EntityName;
            }

            foreach (string entityName in entitiesToCheck)
            {
                EntityPersistenceTest(invalidUpdates, entityName, nop);
            }

            if (invalidUpdates.Count > 0)
            {
                //if (logError.IsDebugEnabled)
                //{
                Console.WriteLine("  ");
                Console.WriteLine("------ INVALID UPDATES -------");
                invalidUpdates.ForEach(Console.WriteLine);
                Console.WriteLine("------------------------------");
                //}
            }
            Assert.AreEqual(0, invalidUpdates.Count, "Has unexpected updates.");
        }

        private void EntityPersistenceTest(ICollection<string> invalidUpdates,
                                           string entityName, IInterceptor nop)
        {
            const string queryTemplate = "select e.{0} from {1} e";
            string msg = "s--------" + entityName;
            Console.WriteLine(msg);

            using (ISession s = sessions.OpenSession(nop))
            using (ITransaction tx = s.BeginTransaction())
            {
                IList entityIds = null;
                try
                {
                    string queryString = string.Format(queryTemplate, DefaultIdName, entityName);
                    entityIds = s.CreateQuery(queryString).SetMaxResults(1).List();
                }
                catch (Exception e)
                {
                    Console.WriteLine("Possible METEORITE:" + e.Message);
                }

                if (entityIds != null)
                {
                    if (entityIds.Count == 0 || entityIds[0] == null)
                    {
                        Console.WriteLine("No instances");
                    }
                    else
                    {
                        if (entityIds.Count > 1)
                        {
                            msg = ">Has " + entityIds.Count + " subclasses";
                            Console.WriteLine(msg);
                        }
                        object entityId = entityIds[0];
                        try
                        {
                            s.Get(entityName, entityId);
                            try
                            {
                                s.Flush();
                            }
                            catch (Exception ex)
                            {
                                string emsg = string.Format("EXCEPTION - Flushing entity [#{0}]: {1}", entityId, ex.Message);
                                Console.WriteLine(emsg);
                                invalidUpdates.Add(emsg);
                            }
                        }
                        catch (Exception ex)
                        {
                            string emsg = string.Format("EXCEPTION - Getting [#{0}]: {1}", entityId, ex.Message);
                            invalidUpdates.Add(emsg);
                            Console.WriteLine(emsg);
                        }
                    }
                    tx.Rollback();
                }
            }
            msg = "e--------" + entityName;
            Console.WriteLine(msg);
        }

        private class NoUpdateInterceptor : EmptyInterceptor
        {
            private readonly IList<string> invalidUpdates;
            private ISession currentSession;

            public NoUpdateInterceptor(IList<string> invalidUpdates)
            {
                this.invalidUpdates = invalidUpdates;
            }

            public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState,
                                              string[] propertyNames, IType[] types)
            {
                string msg = " FlushDirty :" + entity.GetType().FullName;
                Console.WriteLine(msg);
                Console.WriteLine("Dirty properties: \n-{0}", string.Join("\n-", GetDirtyProperties(entity)));
                invalidUpdates.Add(msg);
                return false;
            }

            public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, IType[] types)
            {
                string msg = " Save       :" + entity.GetType().FullName;
                Console.WriteLine(msg);
                invalidUpdates.Add(msg);
                return false;
            }

            public override void OnDelete(object entity, object id, object[] state, string[] propertyNames, IType[] types)
            {
                string msg = " Delete     :" + entity.GetType().FullName;
                Console.WriteLine(msg);
                invalidUpdates.Add(msg);
            }

            public override void SetSession(ISession session)
            {
                this.currentSession = session;
            }

            private string[] GetDirtyProperties(Object entity)
            {
                String className = NHibernateProxyHelper.GuessClass(entity).FullName;
                ISessionImplementor sessionImpl = currentSession.GetSessionImplementation();
                IEntityPersister persister = sessionImpl.Factory.GetEntityPersister(className);
                EntityEntry oldEntry = sessionImpl.PersistenceContext.GetEntry(entity);

                if ((oldEntry == null) && (entity is INHibernateProxy))
                {
                    var proxy = entity as INHibernateProxy;
                    Object obj = sessionImpl.PersistenceContext.Unproxy(proxy);
                    oldEntry = sessionImpl.PersistenceContext.GetEntry(obj);
                }

                Object[] oldState = oldEntry.LoadedState;
                Object[] currentState = persister.GetPropertyValues(entity, sessionImpl.EntityMode);
                Int32[] dirtyProps = persister.FindDirty(currentState, oldState, entity, sessionImpl);

                return dirtyProps.Select(d => string.Format("{0} => Old state: {1}. Current state: {2}. {3}",
                    persister.PropertyNames[d],
                    oldState[d] ?? "null",
                    currentState[d] ?? "null",
                    oldState[d] == null
                       && currentState[d] != null ? "Your property should be nullable or your data is corrupted" : string.Empty)).ToArray();
            }

        }




    }
}
